﻿using Raider.WinForms.Actions.Utils;
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;

namespace Raider.WinForms.Actions.Linkage
{
    /// <summary>
    /// Links property of the client component with the property of the action.
    /// </summary>
    public class ActionPropertyLinker : ActionMemberLinker
    {
        protected static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> TargetPropertyCache =
            new Dictionary<Type, Dictionary<string, PropertyInfo>>();

        public ActionPropertyLinker(PropertyInfo actionProperty, string clientProperty, IList<Type> clientTypes)
            : base(actionProperty, clientTypes)
        {
            ClientProperty = clientProperty;
        }

        public string ClientProperty
        {
            get;
            private set;
        }

        public override bool LinkMember(IComponent client, IAction action)
        {
            Type targetType = client.GetType();
            
            if (!IsClientSupported(targetType))
                return false;

            PropertyInfo actionProperty = (PropertyInfo)ActionMember;
            PropertyInfo targetProperty = GetCachedTargetProperty(targetType, ClientProperty);

            if (targetProperty == null)
            {
                targetProperty = targetType.GetProperty(ClientProperty);

                if (targetProperty == null)
                    throw new InvalidOperationException("Client object of type " + targetType +
                                                        " does not have a property named " +
                                                        ClientProperty);

                TargetPropertyCache[targetType].Add(ClientProperty, targetProperty);
            }

            object value = actionProperty.GetValue(action, null);

            if (DesignUtils.DesignMode)
            {
                var props = TypeDescriptor.GetProperties(client);
                foreach (PropertyDescriptor prop in props)
                {
                    if (prop.Name == ClientProperty)
                    {
                        prop.SetValue(client, value);
                        break;
                    }
                }
            }
            else
            {
                targetProperty.SetValue(client, value, null);
            }

            return true;
        }

        public override bool UnLinkMember(IComponent client, IAction action)
        {
            Type targetType = client.GetType();

            if (!IsClientSupported(targetType))
                return false;

            // Properties don't need to be unlinked.
            return true;
        }

        protected static PropertyInfo GetCachedTargetProperty(Type clientType, string clientProperty)
        {
            Dictionary<string, PropertyInfo> targetMembers;
            if (TargetPropertyCache.TryGetValue(clientType, out targetMembers))
            {
                PropertyInfo member;
                if (targetMembers.TryGetValue(clientProperty, out member))
                {
                    return member;
                }
            }
            else
            {
                TargetPropertyCache.Add(clientType, new Dictionary<string, PropertyInfo>());
            }

            return null;
        }
    }
}
